#ifndef ROUTE_H
#define ROUTE_H

#include <string.h>
#include <sstream>
#include "Def.h"
#include "Nodes.h"
#include "PNets.h"

class Route{

  public:
    int gridX, gridY, numLayer;
    vector<int> vCap;
    vector<int> hCap;
    vector<int> mWireWidth;
    vector<int> mWireSpace;
    vector<int> viaSpace;
    int llx,lly;
    int tileWidth, tileHeight;
    int blockagePorosity;

    vector< vector< vector<int> > > GridTerminalBlockageDBV;
    vector< vector< vector<int> > > GridTerminalBlockageDBH;
    vector< vector< vector<bool> > > GridTerminalBlockageDBV_visited;
    vector< vector< vector<bool> > > GridTerminalBlockageDBH_visited;

    vector<Node*> terminals;
    vector<Node*> blockageNodes;
    vector<Blockage> blockages;
    // parser
    bool parse(string fileName){
      ;
      ifstream inp(fileName.c_str());
      if(!inp.is_open()){
        cerr<<"Can not open "<<fileName<<endl;
        exit(1);
      }

      char line[1024];
      char name[256];
      Node *node;
      int layer, bl;
      NODE_ITERATOR it;
      stringstream ss;
      int numTerminals, numBlockages;

      for(int i=0; i<4; ++i)
        inp.getline(line, 1024);
      inp.getline(line, 1024);
      sscanf(line,"Grid                      :  %d    %d    %d", &gridX, &gridY, &numLayer);
      printf("Grid                      :  %d    %d    %d\n", gridX, gridY, numLayer);
      vCap.resize(numLayer);
      hCap.resize(numLayer);
      mWireWidth.resize(numLayer);
      mWireSpace.resize(numLayer);
      viaSpace.resize(numLayer);

      inp.getline(line, 1024);
      ss.str(line);
      ss>>name;
      ss>>name;
      for(int i=0; i<numLayer; ++i)
        ss>>vCap[i];
      ss.clear();

      inp.getline(line, 1024);
      ss.str(line);
      ss>>name;
      ss>>name;
      for(int i=0; i<numLayer; ++i)
        ss>>hCap[i];
      ss.clear();

      inp.getline(line, 1024);
      ss.str(line);
      ss>>name;
      ss>>name;
      for(int i=0; i<numLayer; ++i)
        ss>>mWireWidth[i];
      ss.clear();

      inp.getline(line, 1024);
      ss.str(line);
      ss>>name;
      ss>>name;
      for(int i=0; i<numLayer; ++i)
        ss>>mWireSpace[i];
      ss.clear();

      inp.getline(line, 1024);
      ss.str(line);
      ss>>name;
      ss>>name;
      for(int i=0; i<numLayer; ++i)
        ss>>viaSpace[i];
      ss.clear();

      inp.getline(line, 1024);
      sscanf(line,"GridOrigin                :  %d     %d", &llx, &lly);    
      printf("GridOrigin                :  %d     %d\n", llx, lly); 

      inp.getline(line, 1024);
      sscanf(line,"TileSize                  :  %d     %d", &tileWidth, &tileHeight);
      printf("TileSize                  :  %d     %d\n", tileWidth, tileHeight);

      inp.getline(line, 1024);
      sscanf(line,"BlockagePorosity          :  %d", &blockagePorosity);     

      inp.getline(line, 1024);
      inp.getline(line, 1024);
      sscanf(line, "NumNiTerminals  :  %d",&numTerminals);
      printf("Number of NiTerminals  :  %d\n",numTerminals);

      inp.getline(line, 1024);
      terminals.resize(numTerminals);

      for(int i=0; i<numTerminals; ++i){
        inp.getline(line, 1024);
        sscanf(line,"             %s     %d",name, &layer);
        node = new Node();
        node->name = name;
        it = nodes.nodes.find(node);
        if(it==nodes.nodes.end())
          it = nodes.terminals.find(node);
        if(it==nodes.terminals.end()){
          cerr<<"Not found Node"<<name<<endl;
          exit(1);
        }
        delete node;
        node = *it;
        node->layer_id = layer;
        terminals[i] = node;
      }

      inp.getline(line, 1024);
      inp.getline(line, 1024);
      sscanf(line, "NumBlockageNodes  :  %d",&numBlockages);
      printf("Number of Blockage Nodes  :  %d\n",numBlockages);
      inp.getline(line, 1024);
      blockageNodes.resize(numBlockages);

      for(int i=0; i<numBlockages; ++i){
        ss.clear();
        inp.getline(line, 1024);
        ss.str(line);
        ss>>name;
        node = new Node();
        node->name = name;
        it = nodes.nodes.find(node);
        if(it==nodes.nodes.end())
          it = nodes.terminals.find(node);
        if(it==nodes.terminals.end()){
          cerr<<"Not found Node"<<name<<endl;
          exit(1);
        }
        delete node;
        node = *it;
        node->isBlockage = true;
        ss>>bl;
        node->layer_list.resize(bl);
        for(int j=0; j<bl; ++j)
          ss>>node->layer_list[j];
        blockageNodes[i] = node;
      }			
      inp.close();
      return true;
    }

    bool addBlockages(int llx, int lly, int hrx, int hry, int layer){
      int startX,startY,endX,endY;
      layer-=1;
      if(hCap[layer]>0){ // horizontal
        startX = llx/tileWidth*tileWidth;
        startY = lly/tileHeight*tileHeight+(lly%tileHeight!=0)*tileHeight;
        endX = hrx/tileWidth*tileWidth+(hrx%tileWidth!=0)*tileWidth;
        endY = hry/tileHeight*tileHeight;

        for(int i=startX; i<endX; i+=tileWidth){
          if((i/tileWidth)==(gridX-1))
            break;
          for(int j=startY; j<=endY; j+=tileHeight){
            Blockage b;
            b.p1.x=i;
            b.p1.y=b.p2.y=j;
            b.p2.x=i+tileWidth;
            b.p1.z=b.p2.z=layer;
            b.cap = blockagePorosity;
            if(b.p1.x<llx)
              b.cap += ((float)(llx-b.p1.x))/tileWidth*(hCap[layer]-blockagePorosity);
            if(b.p2.x>hrx)
              b.cap += ((float)(b.p2.x-hrx))/tileWidth*(hCap[layer]-blockagePorosity);
            blockages.push_back(b);
          }
        }
      }
      else if(vCap[layer]>0){ //vertical
        startX = llx/tileWidth*tileWidth + (llx%tileWidth!=0)*tileWidth ;
        startY = lly/tileHeight*tileHeight;
        endX = hrx/tileWidth*tileWidth;
        endY = hry/tileHeight*tileHeight + (hry%tileHeight!=0)*tileHeight;

        for(int i=startY; i<endY; i+=tileHeight){
          if((i/tileHeight)==(gridY-1))
            break;						
          for(int j=startX; j<=endX; j+=tileWidth){
            Blockage b;
            b.p1.y=i;
            b.p1.x=b.p2.x=j;
            b.p2.y=i+tileHeight;
            b.p1.z=b.p2.z=layer;
            b.cap = blockagePorosity;
            if(b.p1.y<lly)
              b.cap += ((float)(lly-b.p1.y))/tileHeight*(vCap[layer]-blockagePorosity);
            if(b.p2.y>hry)
              b.cap += ((float)(b.p2.y-hry))/tileHeight*(vCap[layer]-blockagePorosity);
            blockages.push_back(b);
          }
        }
      }			
      return true;
    }

    void get_blockage_overlap(int obj_lx, int obj_ly, int obj_dx, int obj_dy, int layer){

      if((obj_lx + obj_dx) > (llx + gridX*tileWidth))
      {
        obj_dx = llx + gridX*tileWidth - obj_lx;
      }
      if(obj_lx < llx)
      {
        obj_dx -= (llx - obj_lx);
        obj_lx = llx;
      }
      if((obj_ly + obj_dy) > (lly + gridY*tileHeight))
      {
        obj_dy = lly + gridY*tileHeight - obj_ly;
      }
      if(obj_ly < lly)
      {
        obj_dy -= (lly - obj_ly);
        obj_ly = lly;
      }

      int obj_hx = obj_lx + obj_dx;
      int obj_hy = obj_ly + obj_dy;


      int lx_index = int((obj_lx - llx)/tileWidth);
      int ly_index = int((obj_ly - lly)/tileHeight);
      int hx_index = int((obj_hx - llx)/tileWidth);
      int hy_index = int((obj_hy - lly)/tileHeight);


      if(obj_lx == (lx_index*tileWidth + llx))
      {
        lx_index--;
      }
      if(obj_ly == (ly_index*tileHeight + lly))
      {
        ly_index--;
      }


      lx_index = min(gridX-1, max(0, lx_index));
      ly_index = min(gridY-1, max(0, ly_index));
      hx_index = min(gridX-1, max(0, hx_index));
      hy_index = min(gridY-1, max(0, hy_index));


      char layer_string;
      if(hCap[layer-1] > 0)
      {
        layer_string = 'H';
      }
      else if(vCap[layer-1] > 0)
      {
        layer_string = 'V';
      }
      else
      {
        printf( "WARNING: Layer(layer) has zero cap.\n");
        return;
      }
      int x,y;
      vector<int> v_block(gridY,0);
      vector<int> h_block(gridX,0);

      if((layer_string == 'V') && (hy_index > ly_index))
      {
        if(hx_index == lx_index)
        {
          v_block[0] = obj_dx;
        }
        else
        {
          v_block[0] = (lx_index+1)*tileWidth + llx - obj_lx;
          v_block[hx_index-lx_index] = obj_hx - hx_index*tileWidth - llx;
          for(x=lx_index+1; x<hx_index; x++)
          {
            v_block[x-lx_index] = tileWidth;
          }
        }

        for(x=lx_index; x<=hx_index; x++)
        {
          if(v_block[x-lx_index] <= 0)
            continue;
          for(y=ly_index; y<hy_index; y++)
          {
            GridTerminalBlockageDBV[layer-1][x][y] += v_block[x-lx_index];
            GridTerminalBlockageDBV_visited[layer-1][x][y]=true;

          }
        }
      }

      else if((layer_string == 'H') && (hx_index > lx_index))
      {

        if(hy_index == ly_index)
        {
          h_block[0] = obj_dy;
        }
        else
        {
          h_block[0] = (ly_index+1)*tileHeight + lly - obj_ly;
          h_block[hy_index-ly_index] = obj_hy - hy_index*tileHeight - lly;
          for(y=ly_index+1; y<hy_index; y++)
          {
            h_block[y-ly_index] = tileHeight;
          }
        }

        for(y=ly_index; y<=hy_index; y++)
        {
          if(h_block[y-ly_index] <= 0)
            continue;
          for(x=lx_index; x<hx_index; x++)
          {
            GridTerminalBlockageDBH[layer-1][x][y] += h_block[y-ly_index];
            GridTerminalBlockageDBH_visited[layer-1][x][y]=true;
          }
        }
      }
      else
      {
        // no cap adjustment required (blockage is completely within tile)
      }
    }

    bool extractBlockages(void){
      cout<<"Detect Bloackages"<<endl;

      Node *node;

      for(uint32_t i=0; i<blockageNodes.size(); ++i){
        node=blockageNodes[i];


        if(node->shapes.size()){
          for(uint32_t j=0; j<node->shapes.size(); ++j){
            for(uint32_t k=0; k<node->layer_list.size(); ++k){
              addBlockages(node->shapes[j].ll_xcoord, node->shapes[j].ll_ycoord,
                  node->shapes[j].ll_xcoord + node->shapes[j].width, 
                  node->shapes[j].ll_ycoord+node->shapes[j].height,node->layer_list[k]);
            }
          }
        }
        else{
          for(uint32_t k=0; k<node->layer_list.size(); ++k){
            addBlockages(node->ll_xcoord,node->ll_ycoord,
                node->ll_xcoord+node->width, node->ll_ycoord+node->height,
                node->layer_list[k]);
          }
        }
      }
      cout<<"Number of blockages: "<<blockages.size()<<endl;
      return true;
    }

    bool extractBlockages_2( Box box ){
      cout<<"Detect Bloackages"<<endl;
      GridTerminalBlockageDBV.resize(numLayer);
      GridTerminalBlockageDBH.resize(numLayer);
      GridTerminalBlockageDBV_visited.resize(numLayer);
      GridTerminalBlockageDBH_visited.resize(numLayer);

      for(int z=0; z<numLayer; ++z){
        GridTerminalBlockageDBV[z].resize(gridX);
        GridTerminalBlockageDBH[z].resize(gridX);
        GridTerminalBlockageDBV_visited[z].resize(gridX);
        GridTerminalBlockageDBH_visited[z].resize(gridX);
        for(int x=0; x<gridX; ++x){
          GridTerminalBlockageDBV[z][x].resize(gridY,0);
          GridTerminalBlockageDBH[z][x].resize(gridY,0);
          GridTerminalBlockageDBV_visited[z][x].resize(gridY,false);
          GridTerminalBlockageDBH_visited[z][x].resize(gridY,false);
        }
      }

      Node *node;

      for(uint32_t i=0; i<blockageNodes.size(); ++i){
        node=blockageNodes[i];
        if(node->shapes.size()){
          for(uint32_t j=0; j<node->shapes.size(); ++j){
            for(uint32_t k=0; k<node->layer_list.size(); ++k){
              if(node->layer_list[k]==1)
                continue;
              get_blockage_overlap(node->shapes[j].ll_xcoord, node->shapes[j].ll_ycoord,
                  node->shapes[j].width, node->shapes[j].height,node->layer_list[k]);
            }
          }
        }
        else{
          for(uint32_t k=0; k<node->layer_list.size(); ++k){
            if(node->layer_list[k]==1)
              continue;
            get_blockage_overlap(node->ll_xcoord,node->ll_ycoord,
                node->width, node->height,
                node->layer_list[k]);
          }
        }
      }

      for(int x=0; x<gridX; ++x){
        for(int y=0; y<gridY; ++y){
          for(int z=1; z<numLayer; ++z){

            int col = x;
            int row = y;
            int layer = z+1;
            if(GridTerminalBlockageDBH_visited[z][x][y])
            {
              bool  in_box = x*tileWidth>=box.llx
                && x*tileWidth<box.llx+box.dx
                && y*tileHeight>=box.lly
                && y*tileHeight<=box.lly+box.dy;
              if ( !in_box )
                continue;

              int val = GridTerminalBlockageDBH[z][x][y];
              int blocked = int((1.0 - blockagePorosity) * val);
              if(hCap[layer-1] <= 0){
                cout<< "Max H_Capacity for layer "<<(layer)<<" is zero.\n"<<endl;
                exit(1);
              }

              int max_cap = hCap[layer-1];
              int max_space = tileHeight;
              float available_space = ((float)(max_space - blocked)) / max_space;
              int adjusted_cap = int(max_cap * available_space);
              adjusted_cap = max(0, adjusted_cap);
              int wire_width   = mWireWidth[layer-1];
              int wire_spacing = mWireSpace[layer-1];
              int num_tracks = int(adjusted_cap / (wire_width + wire_spacing));
              adjusted_cap = num_tracks * (wire_width + wire_spacing);

              int new_col = col+1;
              Blockage b;
              b.p1.x=col; b.p1.y=row; b.p1.z=layer;
              b.p2.x=new_col; b.p2.y=row; b.p2.z=layer;
              b.cap = adjusted_cap;

              blockages.push_back(b);
            }

            if(GridTerminalBlockageDBV_visited[z][x][y])
            {
              //    inside box
              bool  in_box = x*tileWidth>=box.llx
                && x*tileWidth<=box.llx+box.dx
                && y*tileHeight>=box.lly
                && y*tileHeight<box.lly+box.dy;
              if ( !in_box )
                continue;
              int val = GridTerminalBlockageDBV[z][x][y];
              int blocked = int((1.0 - blockagePorosity) * val);

              if(vCap[layer-1] <= 0){
                cout<< "Max V_Capacity for layer(layer) is zero.\n";
                exit(1);
              }

              int max_cap = vCap[layer-1];
              int max_space = tileWidth;
              float available_space = ((float)(max_space - blocked)) / max_space;
              int adjusted_cap = int(max_cap * available_space);
              adjusted_cap = max(0, adjusted_cap);
              int wire_width   = mWireWidth[layer-1];
              int wire_spacing = mWireSpace[layer-1];

              int num_tracks = int(adjusted_cap / (wire_width + wire_spacing));
              adjusted_cap = num_tracks * (wire_width + wire_spacing);

              int new_row = row+1;
              Blockage b;
              b.p1.x=col; b.p1.y=row; b.p1.z=layer;
              b.p2.x=col; b.p2.y=new_row; b.p2.z=layer;
              b.cap = adjusted_cap;

              blockages.push_back(b);
            }

          }
        }
      }
      cout<<"Number of blockages: "<<blockages.size()<<endl;
      GridTerminalBlockageDBH_visited.clear();
      GridTerminalBlockageDBV_visited.clear();
      GridTerminalBlockageDBH.clear();
      GridTerminalBlockageDBV.clear();
      return true;
    }

    bool wireISPD08(string fileName){

      cout<<"writing "<<fileName<<endl;
      FILE *fp = fopen(fileName.c_str(),"w");
      if(fp==NULL){
        cerr<<"Can not open "<<fileName<<endl;
        exit(1);
      }
      PCoarseNet *net;
      Blockage b;
      int size;

      fprintf(fp,"grid	%d %d %d\n",gridX,gridY,numLayer);

      fprintf(fp,"vertical cap	");
      for(int i=0; i<numLayer; ++i)
        if(i==(numLayer-1))
          fprintf(fp,"%d\n",vCap[i]);
        else
          fprintf(fp,"%d ",vCap[i]);

      fprintf(fp,"horizontal cap	");
      for(int i=0; i<numLayer; ++i)
        if(i==(numLayer-1))
          fprintf(fp,"%d\n",hCap[i]);
        else
          fprintf(fp,"%d ",hCap[i]);

      fprintf(fp,"minimum width	");
      for(int i=0; i<numLayer; ++i)
        if(i==(numLayer-1))
          fprintf(fp,"1\n");
        else
          fprintf(fp,"1 ");

      fprintf(fp,"minimum spacing	");
      for(int i=0; i<numLayer; ++i)
        if(i==(numLayer-1))
          fprintf(fp,"1\n");
        else
          fprintf(fp,"1 ");

      fprintf(fp,"via spacing	");
      for(int i=0; i<numLayer; ++i)
        if(i==(numLayer-1))
          fprintf(fp,"1\n");
        else
          fprintf(fp,"1 ");

      fprintf(fp,"%d %d %d %d\n\n",llx,lly,tileWidth,tileHeight);

      int netSize = pnets.nets.size();
      fprintf(fp,"num net %d\n",netSize);
      for(int i=0; i<netSize; ++i){
        net = pnets.nets[i];
        size = net->points.size();
        fprintf(fp,"%s %d %d 1\n",net->name.c_str(), i, size);
        for(int j=0; j<size; ++j){
          point p = net->points[j];
          fprintf(fp,"%d	%d	%d\n",p.x,p.y,p.z);
        }
      }

      size = blockages.size();
      fprintf(fp,"\n\n%d\n",size);
      for(int i=0; i<size; ++i){
        b = blockages[i];

        fprintf(fp,"%d %d %d	%d %d %d	%d\n",b.p1.x,b.p1.y,b.p1.z,
            b.p2.x,b.p2.y,b.p2.z,b.cap);
      }
      fclose(fp);
      return true;		
    }

};	

extern Route route;

#endif

